﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Diggins.Jigsaw;
using Domains;

namespace Parser
{
    class Evaluator3
    {
        public Domain Evaluate(Node n, State state)
        {
            switch (n.Label)
            {
                case "Program":
                    {
                        Domain result = null;
                        for (int i = 0; i < n.Count; i++)
                        {
                            result = Evaluate(n[i], state);
                        }
                        return result;
                    }
                case "VarDecl":
                    {

                        return Lambda.Utilities.bindingCommand(state, new Ide(n[0].Text),
                                                               ((Evaluate(n[1], state) as DomainPair).Fst) as Value);
                    }
                case "output":
                    {
                        var eval = Evaluate(n[0], state) as DomainPair;
                        return Lambda.Utilities.OutputCommand(state, eval.Fst as Value);
                    }
                case "If":
                    {
                        Node uselessNode = n[0];
                        var eval = Evaluate(uselessNode[0], state) as DomainPair;

                        return
                            ((((Lambda.Utilities.checkBool(eval.Fst as Bool)(state)) as DomainPair).Fst) as Bool).Value
                                ? Evaluate(n[1], state)
                                : Evaluate(n[2], state);
                    }
                case "Else":
                    {
                        return Evaluate(n[0], state);
                    }
                case "While":
                    {
                        Node uselessNode = n[0];
                        var eval = Evaluate(uselessNode[0], state) as DomainPair;

                        return ((((Lambda.Utilities.checkBool(eval.Fst)(state)) as DomainPair).Fst) as Bool).Value
                                   ? Evaluate(n, (Evaluate(n[1], state) as State))
                                   : Lambda.Utilities.donothing(state);
                    }
                case "Block":
                    {
                        Domain result = null;
                        for (int i = 0; i < n.Count; i++)
                        {
                            result = Evaluate(n[i], state);
                        }
                        return result;
                    }
                case "Expr":
                    {
                        switch (n[0].Label)
                        {
                            case "read":
                                {
                                    return Lambda.Utilities.ReadExpression(state);
                                }
                            case "False":
                                return Lambda.Utilities.result(new Bool(true))(state);
                            case "True":
                                return Lambda.Utilities.result(new Bool(true))(state);
                            case "Integer":
                                return Lambda.Utilities.result(new Num(Int32.Parse(n.Text)))(state);
                            case "BinaryExpr":
                                {
                                    switch (n[0][1].Text)
                                    {
                                        case "+":
                                            {
                                                var eval1 = Evaluate(n[0][0], state) as DomainPair;
                                                var eval2 = Evaluate(n[0][2], state) as DomainPair;

                                                if (eval1.Fst is Num && eval2.Fst is Num)
                                                    return Lambda.Utilities.result(new Num((((eval1.Fst) as Num).Value + ((eval2.Fst) as Num).Value)))(state);
                                                else
                                                {
                                                    return new Error();
                                                }
                                            }
                                        case "=":
                                            {
                                                var eval1 = Evaluate(n[0][0], state) as DomainPair;
                                                var eval2 = Evaluate(n[0][2], state) as DomainPair;

                                                if (eval1.Fst is Bool && eval2.Fst is Bool)
                                                    return Lambda.Utilities.result(new Bool((((eval1.Fst) as Bool).Value== ((eval2.Fst) as Bool).Value)))(state);
                                                else if (eval1.Fst is Num && eval2.Fst is Num)
                                                    return Lambda.Utilities.result(new Bool((((eval1.Fst) as Num).Value == ((eval2.Fst) as Num).Value)))(state);
                                                else
                                                {
                                                    return new Error();
                                                }
                                            }
                                        default: throw new Exception("unsupported binary operation");
                                    }
                                }
                            case "PrefixExpr":
                                {
                                    switch (n[0][0].Text)
                                    {
                                        case "not":
                                            {
                                                var uselessNode = n[0][1];
                                                var eval = Evaluate(uselessNode[0], state) as DomainPair;
                                                return
                                                    Lambda.Utilities.result(new Bool(!((((Lambda.Utilities.checkBool(eval.Fst)(state)) as DomainPair).Fst) as Bool).Value))(state);
                                            }
                                        default : throw new Exception("unsupported unary operation");
                                    }
                                }
                            case "Identifier":
                                {
                                    return Lambda.Utilities.IdentifierExpression(state, new Ide(n.Text)); 
                                }
                        }
                        break;
                    }
                case "Identifier":
                    {
                        return Lambda.Utilities.IdentifierExpression(state, new Ide(n.Text));                       
                    }
                case "False":
                    return Lambda.Utilities.result(new Bool(true))(state);
                case "True":
                    return Lambda.Utilities.result(new Bool(true))(state);
                case "Integer":
                    return Lambda.Utilities.result(new Num(Int32.Parse(n.Text)))(state);
                default: throw new Exception("unsupported comman or exprecion");
            }
            return new Error();
        }
    
    }
}
